SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
6.2 KB · 121 lines tsx
Raw Blame History
1'use client';23import { useActionState } from 'react';4import { addItemAction, updateItemAction } from '@/lib/account/actions';5import { idle, type ActionState } from '@/lib/auth/state';6import { Field, FormMessage, Select, SubmitButton, TextArea, TextInput } from '@/components/account/form';7import { AssetPicker, type PickedAsset } from '@/components/account/asset-picker';89const CURRENCIES = ['USD', 'CAD', 'EUR', 'GBP', 'JPY', 'CHF', 'AUD'];10const GRADERS = ['', 'psa', 'bgs', 'cgc', 'sgc', 'tag', 'ace', 'cbcs', 'wata', 'vga', 'pcgs', 'ngc'];1112export interface ItemInitial {13  itemId: string;14  asset: PickedAsset;15  quantity: number;16  acquiredAt: string | null;17  purchasePrice: number | null;18  purchaseCurrency: string | null;19  source: string | null;20  grader: string | null;21  grade: string | null;22  certificationNumber: string | null;23  serial: string | null;24  condition: string | null;25  notes: string | null;26  tags: string[];27  manualValueUsd: number | null;28  soldAt: string | null;29  soldPriceUsd: number | null;30}3132export function AddItemForm({ collectionId }: { collectionId: string }) {33  const [state, action] = useActionState(addItemAction, idle);34  return <ItemFields state={state} action={action} collectionId={collectionId} submitLabel="Add to collection" />;35}3637export function EditItemForm({ initial }: { initial: ItemInitial }) {38  const [state, action] = useActionState(updateItemAction, idle);39  return <ItemFields state={state} action={action} initial={initial} submitLabel="Save item" />;40}4142function ItemFields({ state, action, collectionId, initial, submitLabel }: { state: ActionState; action: (fd: FormData) => void; collectionId?: string; initial?: ItemInitial; submitLabel: string }) {43  return (44    <form action={action} className="space-y-4" noValidate key={state.ok && !initial ? String(state.data?.itemId) : 'form'}>45      {collectionId ? <input type="hidden" name="collectionId" value={collectionId} /> : null}46      {initial ? <input type="hidden" name="itemId" value={initial.itemId} /> : null}47      <FormMessage state={state} />48      {initial ? <input type="hidden" name="assetId" value={initial.asset.id} /> : null}49      <AssetPicker initial={initial?.asset ?? null} error={state.fieldErrors?.assetId} withVariant={!initial} />50      <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">51        <Field label="Quantity" name="quantity" error={state.fieldErrors?.quantity}>52          <TextInput name="quantity" type="number" min={1} max={10000} defaultValue={initial?.quantity ?? 1} required />53        </Field>54        <Field label="Acquired on" name="acquiredAt" error={state.fieldErrors?.acquiredAt}>55          <TextInput name="acquiredAt" type="date" defaultValue={initial?.acquiredAt ?? ''} max={new Date().toISOString().slice(0, 10)} />56        </Field>57        <Field label="Purchase price (per unit)" name="purchasePrice" error={state.fieldErrors?.purchasePrice}>58          <TextInput name="purchasePrice" type="number" min={0} step="0.01" defaultValue={initial?.purchasePrice ?? ''} placeholder="0.00" />59        </Field>60        <Field label="Currency" name="purchaseCurrency" hint="Converted to USD at the acquisition-date rate.">61          <Select name="purchaseCurrency" defaultValue={initial?.purchaseCurrency ?? 'USD'}>62            {CURRENCIES.map((c) => (63              <option key={c} value={c}>64                {c}65              </option>66            ))}67          </Select>68        </Field>69      </div>70      <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">71        <Field label="Grader" name="grader" hint="Used when no variant is selected.">72          <Select name="grader" defaultValue={initial?.grader ?? ''}>73            {GRADERS.map((g) => (74              <option key={g} value={g}>75                {g ? g.toUpperCase() : 'Raw / none'}76              </option>77            ))}78          </Select>79        </Field>80        <Field label="Grade" name="grade">81          <TextInput name="grade" defaultValue={initial?.grade ?? ''} placeholder="10, 9.5, 9.8, MS65…" maxLength={12} />82        </Field>83        <Field label="Certification #" name="certificationNumber">84          <TextInput name="certificationNumber" defaultValue={initial?.certificationNumber ?? ''} maxLength={40} className="mono-num" />85        </Field>86        <Field label="Serial / reference" name="serial">87          <TextInput name="serial" defaultValue={initial?.serial ?? ''} maxLength={60} className="mono-num" />88        </Field>89      </div>90      <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">91        <Field label="Condition" name="condition">92          <TextInput name="condition" defaultValue={initial?.condition ?? ''} placeholder="sealed, CIB, unworn, full set…" maxLength={40} />93        </Field>94        <Field label="Source" name="source">95          <TextInput name="source" defaultValue={initial?.source ?? ''} placeholder="eBay, Heritage, local show…" maxLength={120} />96        </Field>97        <Field label="Tags" name="tags" hint="Comma-separated, up to 12.">98          <TextInput name="tags" defaultValue={initial?.tags.join(', ') ?? ''} placeholder="grail, sealed, gift" maxLength={200} />99        </Field>100        <Field label="Manual value (USD)" name="manualValueUsd" hint="Fallback only when RareIndex has no valuation; never shown as RIV.">101          <TextInput name="manualValueUsd" type="number" min={0} step="0.01" defaultValue={initial?.manualValueUsd ?? ''} />102        </Field>103      </div>104      {initial ? (105        <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">106          <Field label="Sold on (optional)" name="soldAt">107            <TextInput name="soldAt" type="date" defaultValue={initial.soldAt ?? ''} />108          </Field>109          <Field label="Sold price (USD)" name="soldPriceUsd">110            <TextInput name="soldPriceUsd" type="number" min={0} step="0.01" defaultValue={initial.soldPriceUsd ?? ''} />111          </Field>112        </div>113      ) : null}114      <Field label="Notes" name="notes">115        <TextArea name="notes" defaultValue={initial?.notes ?? ''} maxLength={2000} placeholder="Provenance, storage location, insurance reference…" className="min-h-[70px]" />116      </Field>117      <SubmitButton pendingText="Saving…">{submitLabel}</SubmitButton>118    </form>119  );120}121